--- /dev/null
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "updatesoverlay.h"
+
+#include "gtkintl.h"
+#include "gtkwidget.h"
+
+#include "gsk/gskrendernodeprivate.h"
+
+/* duration before we start fading in us */
+#define GDK_DRAW_REGION_MIN_DURATION 50 * 1000
+/* duration when fade is finished in us */
+#define GDK_DRAW_REGION_MAX_DURATION 200 * 1000
+
+typedef struct {
+ gint64 timestamp;
+ cairo_region_t *region;
+} GtkUpdate;
+
+typedef struct {
+ GQueue *updates;
+ GskRenderNode *last;
+ GtkWidget *widget;
+ guint tick_callback;
+ gulong unmap_callback;
+} GtkWidgetUpdates;
+
+struct _GtkUpdatesOverlay
+{
+ GtkInspectorOverlay parent_instance;
+
+ GHashTable *toplevels; /* widget => GtkWidgetUpdates */
+};
+
+struct _GtkUpdatesOverlayClass
+{
+ GtkInspectorOverlayClass parent_class;
+};
+
+G_DEFINE_TYPE (GtkUpdatesOverlay, gtk_updates_overlay, GTK_TYPE_INSPECTOR_OVERLAY)
+
+static void
+gtk_update_free (gpointer data)
+{
+ GtkUpdate *region = data;
+
+ cairo_region_destroy (region->region);
+ g_slice_free (GtkUpdate, region);
+}
+
+static void
+gtk_widget_updates_release_widget (GtkWidgetUpdates *updates)
+{
+ g_assert (updates->widget);
+ g_signal_handler_disconnect (updates->widget, updates->unmap_callback);
+ if (updates->tick_callback)
+ gtk_widget_remove_tick_callback (updates->widget, updates->tick_callback);
+ updates->tick_callback = 0;
+ updates->widget = NULL;
+}
+
+static void
+gtk_widget_updates_free (gpointer data)
+{
+ GtkWidgetUpdates *updates = data;
+
+ g_queue_free_full (updates->updates, gtk_update_free);
+ g_clear_pointer (&updates->last, gsk_render_node_unref);
+ if (updates->widget)
+ gtk_widget_updates_release_widget (updates);
+
+ g_slice_free (GtkWidgetUpdates, updates);
+}
+
+static gboolean
+gtk_widget_updates_tick (GtkWidget *widget,
+ GdkFrameClock *clock,
+ gpointer data)
+{
+ GtkWidgetUpdates *updates = data;
+ GtkUpdate *draw;
+ gint64 now;
+
+ now = gdk_frame_clock_get_frame_time (clock);
+
+ for (draw = g_queue_pop_tail (updates->updates);
+ draw != NULL && (now - draw->timestamp >= GDK_DRAW_REGION_MAX_DURATION);
+ draw = g_queue_pop_tail (updates->updates))
+ {
+ gtk_update_free (draw);
+ }
+
+ gdk_surface_queue_expose (gtk_widget_get_surface (widget));
+ if (draw)
+ {
+ g_queue_push_tail (updates->updates, draw);
+ return G_SOURCE_CONTINUE;
+ }
+ else
+ {
+ updates->tick_callback = 0;
+ return G_SOURCE_REMOVE;
+ }
+}
+
+static GtkWidgetUpdates *
+gtk_update_overlay_lookup_for_widget (GtkUpdatesOverlay *self,
+ GtkWidget *widget,
+ gboolean create)
+{
+ GtkWidgetUpdates *updates = g_hash_table_lookup (self->toplevels, widget);
+
+ if (updates || !create)
+ return updates;
+
+ updates = g_slice_new0 (GtkWidgetUpdates);
+ updates->updates = g_queue_new ();
+ updates->widget = widget;
+ updates->unmap_callback = g_signal_connect_swapped (widget, "unmap", G_CALLBACK (gtk_widget_updates_release_widget), updates);
+
+ g_hash_table_insert (self->toplevels, g_object_ref (widget), updates);
+ return updates;
+}
+
+static void
+gtk_widget_updates_add (GtkWidgetUpdates *updates,
+ gint64 timestamp,
+ cairo_region_t *region)
+{
+ GtkUpdate *update;
+ GList *l;
+
+ update = g_slice_new0 (GtkUpdate);
+ update->timestamp = timestamp;
+ update->region = region;
+ for (l = g_queue_peek_head_link (updates->updates); l != NULL; l = l->next)
+ {
+ GtkUpdate *u = l->data;
+ cairo_region_subtract (u->region, region);
+ }
+ g_queue_push_head (updates->updates, update);
+ if (updates->tick_callback == 0)
+ updates->tick_callback = gtk_widget_add_tick_callback (updates->widget, gtk_widget_updates_tick, updates, NULL);
+}
+
+static void
+gtk_updates_overlay_snapshot (GtkInspectorOverlay *overlay,
+ GtkSnapshot *snapshot,
+ GskRenderNode *node,
+ GtkWidget *widget)
+{
+ GtkUpdatesOverlay *self = GTK_UPDATES_OVERLAY (overlay);
+ GtkWidgetUpdates *updates;
+ GtkUpdate *draw;
+ gint64 now;
+ GList *l;
+
+ updates = gtk_update_overlay_lookup_for_widget (self, widget, TRUE);
+ now = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget));
+
+ if (updates->last)
+ {
+ cairo_region_t *diff;
+
+ diff = cairo_region_create ();
+ gsk_render_node_diff (updates->last, node, diff);
+ if (cairo_region_is_empty (diff))
+ cairo_region_destroy (diff);
+ else
+ gtk_widget_updates_add (updates, now, diff);
+ }
+ else
+ {
+ cairo_region_t *region;
+ graphene_rect_t bounds;
+
+ gsk_render_node_get_bounds (node, &bounds);
+ region = cairo_region_create_rectangle (&(cairo_rectangle_int_t) {
+ floor (bounds.origin.x),
+ floor (bounds.origin.y),
+ ceil (bounds.origin.x + bounds.size.width) - floor (bounds.origin.x),
+ ceil (bounds.origin.y + bounds.size.height) - floor (bounds.origin.y)
+ });
+ gtk_widget_updates_add (updates, now, region);
+ }
+ g_clear_pointer (&updates->last, gsk_render_node_unref);
+ updates->last = gsk_render_node_ref (node);
+
+ for (l = g_queue_peek_head_link (updates->updates); l != NULL; l = l->next)
+ {
+ double progress;
+ guint i;
+
+ draw = l->data;
+
+ if (now - draw->timestamp < GDK_DRAW_REGION_MIN_DURATION)
+ progress = 0.0;
+ else if (now - draw->timestamp < GDK_DRAW_REGION_MAX_DURATION)
+ progress = (double) (now - draw->timestamp - GDK_DRAW_REGION_MIN_DURATION)
+ / (GDK_DRAW_REGION_MAX_DURATION - GDK_DRAW_REGION_MIN_DURATION);
+ else
+ break;
+
+ for (i = 0; i < cairo_region_num_rectangles (draw->region); i++)
+ {
+ GdkRectangle rect;
+
+ cairo_region_get_rectangle (draw->region, i, &rect);
+ gtk_snapshot_append_color (snapshot,
+ &(GdkRGBA) { 1, 0, 0, 0.4 * (1 - progress) },
+ &GRAPHENE_RECT_INIT(rect.x, rect.y, rect.width, rect.height),
+ "Debug Updates<%g>", progress);
+ }
+ }
+}
+
+static void
+gtk_updates_overlay_queue_draw (GtkInspectorOverlay *overlay)
+{
+ GtkUpdatesOverlay *self = GTK_UPDATES_OVERLAY (overlay);
+ GHashTableIter iter;
+ gpointer widget;
+
+ g_hash_table_iter_init (&iter, self->toplevels);
+ while (g_hash_table_iter_next (&iter, &widget, NULL))
+ gdk_surface_queue_expose (gtk_widget_get_surface (widget));
+}
+
+static void
+gtk_updates_overlay_dispose (GObject *object)
+{
+ GtkUpdatesOverlay *self = GTK_UPDATES_OVERLAY (object);
+
+ g_hash_table_unref (self->toplevels);
+
+ G_OBJECT_CLASS (gtk_updates_overlay_parent_class)->dispose (object);
+}
+
+static void
+gtk_updates_overlay_class_init (GtkUpdatesOverlayClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkInspectorOverlayClass *overlay_class = GTK_INSPECTOR_OVERLAY_CLASS (klass);
+
+ overlay_class->snapshot = gtk_updates_overlay_snapshot;
+ overlay_class->queue_draw = gtk_updates_overlay_queue_draw;
+
+ gobject_class->dispose = gtk_updates_overlay_dispose;
+}
+
+static void
+gtk_updates_overlay_init (GtkUpdatesOverlay *self)
+{
+ self->toplevels = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, gtk_widget_updates_free);
+}
+
+GtkInspectorOverlay *
+gtk_updates_overlay_new (void)
+{
+ return g_object_new (GTK_TYPE_UPDATES_OVERLAY, NULL);
+}
+
#include "visual.h"
+#include "updatesoverlay.h"
+#include "window.h"
+
#include "gtkadjustment.h"
#include "gtkbox.h"
#include "gtkcomboboxtext.h"
GtkWidget *debug_box;
GtkWidget *rendering_mode_combo;
+ GtkWidget *updates_switch;
GtkWidget *baselines_switch;
GtkWidget *layout_switch;
GtkWidget *touchscreen_switch;
GtkWidget *texture_rectangle_switch;
GtkAdjustment *focus_adjustment;
+
+ GtkInspectorOverlay *updates_overlay;
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorVisual, gtk_inspector_visual, GTK_TYPE_SCROLLED_WINDOW)
update_font_scale (vis, factor, TRUE, FALSE);
}
+static void
+updates_activate (GtkSwitch *sw,
+ GParamSpec *pspec,
+ GtkInspectorVisual *vis)
+{
+ GtkInspectorVisualPrivate *priv = vis->priv;
+ GtkInspectorWindow *iw;
+ gboolean updates;
+
+ updates = gtk_switch_get_active (sw);
+ iw = GTK_INSPECTOR_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (vis)));
+ if (iw == NULL)
+ return;
+
+ if (updates)
+ {
+ if (priv->updates_overlay == NULL)
+ {
+ priv->updates_overlay = gtk_updates_overlay_new ();
+ gtk_inspector_window_add_overlay (iw, priv->updates_overlay);
+ g_object_unref (priv->updates_overlay);
+ }
+ }
+ else
+ {
+ if (priv->updates_overlay != NULL)
+ {
+ gtk_inspector_window_remove_overlay (iw, priv->updates_overlay);
+ priv->updates_overlay = NULL;
+ }
+ }
+
+ redraw_everything ();
+}
+
static void
baselines_activate (GtkSwitch *sw)
{
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/visual.ui");
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, rendering_mode_combo);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, updates_switch);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, direction_combo);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, baselines_switch);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, layout_switch);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, font_scale_entry);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, font_scale_adjustment);
+ gtk_widget_class_bind_template_callback (widget_class, updates_activate);
gtk_widget_class_bind_template_callback (widget_class, direction_changed);
gtk_widget_class_bind_template_callback (widget_class, rendering_mode_changed);
gtk_widget_class_bind_template_callback (widget_class, baselines_activate);